package com.liyunc.socket.rpc.nio.user;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class RPCClient<T> {
    public static <T> T getRemoteProxyObject(final Class<?> serviceInterface,
                                             final InetSocketAddress address) {
        // 1、将本地的接口调用转换成JDK的动态代理，在动态代理中实现接口的远程调用
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),
            new Class<?>[] {serviceInterface},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object result = null;
                    // 1、进行selector注册，创建selector
                    Selector selector = Selector.open();
                    // 进行socketChannel注册，获取对应的channel并监听
                    SocketChannel clientChannel = SocketChannel.open();
                    clientChannel.configureBlocking(false);
                    clientChannel.connect(address);

                    // 2、进行selector注册，对接完成和读取进行绑定
                    clientChannel.register(selector, SelectionKey.OP_CONNECT);
                    // 获取访问就绪的通道，通过selector方法
                    while (selector.select() > 0) {
                        Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                        while (keyIterator.hasNext()) {
                            SelectionKey key = keyIterator.next();
                            if (key.isReadable()) {
                                SocketChannel readChannel = (SocketChannel) key.channel();
                                ByteBuffer buffer = ByteBuffer.allocate(1024);
                                readChannel.read(buffer);
                                ByteArrayInputStream byteInput =
                                    new ByteArrayInputStream(buffer.array());
                                ObjectInputStream input = new ObjectInputStream(byteInput);
                                result = input.readObject();
                                key.interestOps(SelectionKey.OP_CONNECT);
                            } else if (key.isConnectable()) {
                                SocketChannel socketChannel = (SocketChannel) key.channel();
                                if (socketChannel.finishConnect()) {
                                    key.interestOps(SelectionKey.OP_WRITE);
                                    ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
                                    ObjectOutputStream output = new ObjectOutputStream(byteOutput);
                                    output.writeUTF(serviceInterface.getName());
                                    output.writeUTF(method.getName());
                                    output.writeObject(method.getParameterTypes());
                                    output.writeObject(args);
                                    output.flush();
                                    byte[] bytes = byteOutput.toByteArray();
                                    ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
                                    byteBuffer.put(bytes);
                                    byteBuffer.flip();
                                    socketChannel.write(byteBuffer);
                                    byteBuffer.clear();
                                    socketChannel.register(selector, SelectionKey.OP_READ);
                                }
                            } else {
                                System.out.println("与服务端建立连接失败");
                            }
                        }
                    }
                    return result;
                }
            });
    }
}